#include <at_device.h>
#include <at_socket.h>

#include <dfs_posix.h>
#include <dfs_poll.h>

struct atdwait
{
    rt_list_t node;
    enum at_event_type type;
    uint32_t id;
    struct rt_completion notice;
    int status;

    struct rt_ringbuffer data;
};

static int _atdioc_netdev_register(struct at_device *atd);
static int _atdioc_resp(struct at_device *atd, struct at_resp *rsp);
static int _atdioc_querybuf(struct at_device *atd, struct at_buffer *ab);

static struct atdwait *atd_new_file(enum at_event_type type, uint32_t id, int size)
{
    char *p;
    struct atdwait *w;

    p = rt_malloc(sizeof(struct atdwait) + size);
    rt_memset(p, 0, sizeof(struct atdwait));
    w = (struct atdwait *)p;
    p += sizeof(struct atdwait);
    w->type = type;
    w->status = -ETIMEDOUT;
    w->id = id;
    rt_completion_init(&w->notice);
    rt_list_init(&w->node);
    w->data.buffer_ptr = p;
    w->data.buffer_size = size;

    return w;
}

static int atd_init(struct at_device *atd)
{
    int ret = 0;

    atd->cmdlock = rt_mutex_create("atcmd", 0);
    atd->cmd_req = rt_ringbuffer_create(128);
    atd->cmd_rsp = rt_ringbuffer_create(128);

    rt_wqueue_init(&atd->wq_req);
    rt_wqueue_init(&atd->wq_rsp);
    atd->socket_num = 8;
    atd->sockets = rt_calloc(atd->socket_num, sizeof(struct at_socket));

    return ret;
}

static int atd_fops_open(struct dfs_fd *fd, ...)
{
    int ret;
    struct at_device *atd = (struct at_device*)fd->data;

    ret = atd_init(atd);

    return ret;
}

static int atd_fops_read(struct dfs_fd *fd, void *buf, size_t count, ...)
{
    int ret;
    struct at_device *atd = (struct at_device*)fd->data;

    ret = rt_ringbuffer_get(atd->cmd_req, buf, count);
    rt_wqueue_wakeup(&atd->wq_req, RT_NULL);

    return ret;
}

static int atd_fops_ioctl(struct dfs_fd *fd, int cmd, void *args)
{
    int ret = 0;
    struct at_device *atd = (struct at_device*)fd->data;

    switch (cmd)
    {
    case ATDIOC_MKNETDEV:
    {
        ret = _atdioc_netdev_register(atd); 
    }break;
    case ATDIOC_RESP:
    {
        struct at_resp *rsp = (struct at_resp*)args;

        ret = _atdioc_resp(atd, rsp);
    }break;
    case ATDIOC_QUERYBUF:
    {
        struct at_buffer *ab = (struct at_buffer*)args;

        ret = _atdioc_querybuf(atd, ab);
    }break;
    }

    return ret;
}

static int atd_fops_poll(struct dfs_fd *fd, struct rt_pollreq *req)
{
    int mask = 0;
    struct at_device *atd = (struct at_device*)fd->data;

    if (rt_list_isempty(&atd->reqs_cmdhead))
    {
        rt_poll_add(&atd->wq_req, req);
    }
    else
    {
        mask |= POLLIN;
    }

    return mask;
}

const static struct dfs_file_ops _atd_fops =
{
    .open = atd_fops_open,
    .read = atd_fops_read,
    .ioctl = atd_fops_ioctl,
    .poll = atd_fops_poll,
};

static struct atdwait* atdwait_alloc(enum at_event_type type, uint32_t id)
{
    struct atdwait *w;

    w = rt_calloc(1, sizeof(struct atdwait));
    if (w)
    {
        rt_list_init(&w->node);
        w->type = type;
        rt_completion_init(&w->notice);
        w->id = id;
        w->status = -ETIMEDOUT;
    }

    return w;
}

static int atd_send_req(struct at_device *atd, struct atdwait *w, int timeout)
{
    int ret = 0;

    if (rt_mutex_take(atd->cmdlock, rt_tick_from_millisecond(timeout)) != 0)
        return -ETIMEDOUT;

    rt_list_insert_before(&atd->reqs_cmdhead, &w->node);
    rt_mutex_release(atd->cmdlock);

    rt_wqueue_wakeup(&atd->wq_req, (void*)POLLIN);

    rt_completion_wait(&w->notice, rt_tick_from_millisecond(timeout));
    ret = w->status;

    rt_list_remove(&w->node);

    return ret;
}

static int atd_connect(struct at_socket *as, char *ip, int32_t port, enum at_socket_type type, rt_bool_t is_client)
{
    struct at_device *atd = (struct at_device *)as->device;
    char *buf;
    char t, c;
    int len;
    int timeout = 30;
    struct atdwait *w;
    int ret;

    /* "OTCS/OUCS" open tcp/udp client socket */
    if (type == AT_SOCKET_TCP)
        t = 'T';
    else if (type == AT_SOCKET_UDP)
        t = 'U';

    c = is_client? 'C' : 'S';

    w = atd_new_file(AT_EV_OSK, as->socket, 64);
    if (!w)
    {
        return -ENOMEM;
    }
    buf = w->data.buffer_ptr;

    len = rt_snprintf(buf, 64, "O%c%cS,%d,%s,%d,%d\n",
                      t, c, as->socket, ip, port, timeout);
    w->data.write_index = len;

    ret = atd_send_req(atd, w, (timeout + 1)*1000);
    rt_free(w);

    return ret;
}

static int atd_send(struct at_socket *as, const char *buff, size_t bfsz, enum at_socket_type type)
{
    struct at_device *atd = (struct at_device *)as->device;
    int ret ;
    struct atdwait *w;

    w = atd_new_file(AT_EV_DOUT, as->socket, 0);
    if (!w)
    {
        return -ENOMEM;
    }

    rt_ringbuffer_init(&w->data, (char*)buff, bfsz); //TODO for test

    w->data.write_index = bfsz;
    ret = atd_send_req(atd, w, 10000);
    if (ret == 0)
    {
        if (w->status <= 0)
            ret = -EIO;
        else
            ret = w->status;
    }

    rt_free(w);

    return ret;
}

static int atd_closesocket(struct at_socket *as)
{
    struct at_device *atd = (struct at_device *)as->device;
    char *cmd;
    int ret ;
    struct atdwait *w;

    w = atd_new_file(AT_EV_CLOSE, as->socket, 32);
    if (!w)
    {
        return -ENOMEM;
    }

    cmd = w->data.buffer_ptr;
    w->data.write_index = rt_snprintf(cmd, 32, "CLOS,%d\n", as->socket);
    ret = atd_send_req(atd, w, 10000);
    if (ret == 0)
    {
        if (w->status <= 0)
            ret = -EIO;
        else
            ret = w->status;
    }

    rt_free(w);

    return ret;
}

static int atd_domain_resolve(struct at_device *atd, const char *name, char ip[16])
{
    struct atdwait* w;
    char *cmd;
    int len;
    int ret;

    w = atd_new_file(AT_EV_DMRV, rt_tick_get(), rt_strlen(name) + 16);
    cmd = w->data.buffer_ptr;

    w->data.write_index = rt_snprintf(cmd, w->data.buffer_size, "DMRV,%x,%s\n", w->id, name);

    ret = atd_send_req(atd, w, 10000);
    if (ret == 0)
    {
        if (w->status <= 0)
            ret = -EIO;
        else
            ret = w->status;
        
    }
    rt_ringbuffer_get(&w->data, ip, 16);//TODO
    rt_free(w);

    return 0;
}

static const struct at_socket_ops _atd_sops =
{
    .at_connect = atd_connect,
    .at_send = atd_send,
    .at_closesocket = atd_closesocket,
};

static const struct netdev_ops _atd_nops;

static int _atdioc_netdev_register(struct at_device *atd)
{
    struct netdev *netdev;

    if (atd->netdev)
    {
        return 0;
    }

    netdev = rt_calloc(1, sizeof(struct netdev));
    if (!netdev)
    {
        return -ENOMEM;
    }

    netdev->mtu = 1500;
    netdev->ops = &_atd_nops;
    netdev->hwaddr_len = 6;
    netdev->phy = atd;

    sal_at_netdev_set_pf_info(netdev);
    netdev_register(netdev, atd->parent.parent.name, RT_NULL);
    netdev_low_level_set_status(netdev, RT_TRUE);
    atd->netdev = netdev;
    atd->at_domain_resolve = atd_domain_resolve;

    return 0;
}

static int _atdioc_resp(struct at_device *atd, struct at_resp *rsp)
{
    rt_list_t *head;
    struct atdwait *w;
    int ret = -ENODEV;

    head = &atd->reqs_cmdhead;

    rt_list_for_each_entry(w, struct atdwait, head, node)
    {
        if ((w->id == rsp->id) && (rsp->type == w->type))
        {
            if (rsp->type == AT_EV_DMRV)
            {
                rt_ringbuffer_reset(&w->data);
                rt_ringbuffer_put(&w->data, rsp->data, rsp->status);
            }

            rt_list_remove(&w->node);
            w->status = rsp->status;
            rt_completion_done(&w->notice);

            ret = 0;
        }
    }

    return ret;
}

static int _atdioc_querybuf(struct at_device *atd, struct at_buffer *ab)
{
    int ret = -EAGAIN;
    struct atdwait *w;
    rt_list_t *head = &atd->reqs_cmdhead;

    rt_list_for_each_entry(w, struct atdwait, head, node)
    {
        ab->type = w->type;
        ab->id = w->id;
        ab->data = w->data.buffer_ptr;
        ab->length = w->data.write_index;            
        ret = 0;
        break;
    }

    return ret;
}

static struct at_device *_atd = 0;
int rt_at_device_register(struct at_device *dev, const char *name)
{
    int ret;

    rt_memset(dev, 0, sizeof(*dev));
    dev->socket_ops = &_atd_sops;
    rt_list_init(&dev->reqs_cmdhead);
    rt_list_init(&dev->reqs_dathead);
    rt_list_init(&dev->idle_dathead);
    ret = rt_device_register(&dev->parent, name, 0);

    dev->parent.fops = &_atd_fops;
_atd=dev;
    return ret;
}

static int atc(void)
{
    struct at_socket as;

    as.device = _atd;
    as.socket = 0;
    atd_connect(&as, "192.168.31.8", 8080, AT_SOCKET_TCP, 1);

    return 0;
}
MSH_CMD_EXPORT(atc, atc);
